/* See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Esri Inc. licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.esri.gpt.catalog.schema;
import com.esri.gpt.framework.util.LogUtil;
import com.esri.gpt.framework.util.Val;
import java.io.IOException;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Level;
import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* Validates a document against an XML Schema Definition file.
*/
public class XsdValidator implements ErrorHandler {
// class variables =============================================================
private static HashMap<String,javax.xml.validation.Schema>XSDS;
// instance variables ==========================================================
private ValidationErrors _validationErrors;
private String _xsdLocation = null;
// static initialization
static {
XSDS = new HashMap<String,javax.xml.validation.Schema>();
}
// constructors ================================================================
/** Default constructor. */
public XsdValidator() {}
// properties ==================================================================
/**
* Gets the validation errors.
* @return the validation errors
*/
protected ValidationErrors getValidationErrors() {
return _validationErrors;
}
/**
* Sets the validation errors.
* @param errors validation errors
*/
protected void setValidationErrors(ValidationErrors errors) {
_validationErrors = errors;
if (_validationErrors == null) _validationErrors = new ValidationErrors();
}
// methods =====================================================================
/**
* Append an XSD validation error to the error collection.
* <br/>The error is captured and an exception is thrown.
* @param e the associated SAX exception
*/
private void appendError(SAXParseException e) {
int nLine = e.getLineNumber();
int nCol = e.getColumnNumber();
String sMsg = Val.chkStr(e.getMessage());
if (sMsg.length() == 0) {
sMsg = e.toString();
}
if ((nLine > 0) && (nCol > 0)) {
sMsg = "Line "+nLine+" Column "+nCol+" "+sMsg;
} else if (nLine > 0) {
sMsg = "Line "+sMsg;
}
ValidationError error = new ValidationError();
error.setMessage(sMsg);
error.setReasonCode(ValidationError.REASONCODE_XSD_VIOLATION);
getValidationErrors().add(error);
}
/**
* Caches a reference to an XSD for a schema.
* @param schema the subject schema
*/
public static void cacheXsdReference(Schema schema) {
try {
XsdValidator xsdv = new XsdValidator();
xsdv.newValidator(schema);
} catch (ValidationException e) {
// ignore the exception, it's been logged by the newValidator() method
}
}
/**
* Triggered when a SAX fatal error occurs during XSD validation.
* <br/>The error is captured and an exception is thrown.
* @param e the associated SAX exception
* @throws SAXException the supplied SAXParseException is thrown to end the process
*/
public void fatalError(SAXParseException e) throws SAXException {
appendError(e);
throw e;
}
/**
* Triggered when a SAX error occurs during XSD validation.
* <br/>The error is captured bu no exception is thrown.
* @param e the associated SAX exception
* @throws SAXException part of the implemented method signature but never thrown
*/
public void error(SAXParseException e) throws SAXException {
appendError(e);
}
/**
* Creates a new javax.xml.validation.Validator for a schema.
* <p/>
* A validator will only be created if an XSD has been configured for
* the schema - schema.getXsdLocation().
* <p/>
* The XSD is cached for subsequent use.
* @param schema the subject schema
* @return the validator (null if an XSD has not been configured for the schema)
* @throws ValidationException if the schema's XSD cannot be accessed
*/
private javax.xml.validation.Validator newValidator(Schema schema)
throws ValidationException {
String xsdLocation = Val.chkStr(_xsdLocation);
if (xsdLocation.length() == 0) {
xsdLocation = Val.chkStr(schema.getXsdLocation());
}
if (xsdLocation.length() > 0) {
String sMsg;
ValidationError error;
// find a cached XSD reference,
// if none was found, create the reference and cache
javax.xml.validation.Schema xsd = XSDS.get(xsdLocation);
if (xsd == null) {
javax.xml.validation.SchemaFactory factory =
javax.xml.validation.SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try {
String [] xsdLocations = Val.tokenize(xsdLocation, ",");
if (xsdLocations!=null && xsdLocations.length>1) {
ArrayList<StreamSource> streamSources = new ArrayList<StreamSource>();
for (String systemId : xsdLocations) {
streamSources.add(new StreamSource(systemId));
}
xsd = factory.newSchema(streamSources.toArray(new StreamSource[streamSources.size()]));
XSDS.put(xsdLocation,xsd);
} else {
URL url = new URL(xsdLocation);
xsd = factory.newSchema(url);
XSDS.put(xsdLocation,xsd);
}
} catch (MalformedURLException e) {
sMsg = "Malformed XSD URL, schema="+schema.getKey()+" xsdLocation="+xsdLocation;
LogUtil.getLogger().log(Level.SEVERE,sMsg,e);
error = new ValidationError();
error.setMessage(sMsg);
error.setReasonCode(ValidationError.REASONCODE_XSD_ISINVALID);
getValidationErrors().add(error);
throw new ValidationException(schema.getKey(),
sMsg,getValidationErrors());
} catch (SAXException e) {
sMsg = "Error parsing XSD, schema="+schema.getKey()+" xsdLocation="+xsdLocation;
LogUtil.getLogger().log(Level.SEVERE,sMsg,e);
error = new ValidationError();
error.setMessage(sMsg);
error.setReasonCode(ValidationError.REASONCODE_XSD_ISINVALID);
getValidationErrors().add(error);
throw new ValidationException(schema.getKey(),
sMsg,getValidationErrors());
}
}
return xsd.newValidator();
}
return null;
}
/**
* Validates a document source against the XML Schema Definition file
* associated with a schema.
* @param schema the schema being validated
* @param source the source for the document being validated
* @throws ValidationException if validation errors were located
*/
public void validate(Schema schema, Source source)
throws ValidationException {
setValidationErrors(new ValidationErrors());
if (schema.getXsdLocation().length() > 0) {
try {
javax.xml.validation.Validator validator = newValidator(schema);
validator.setErrorHandler(this);
validator.validate(source);
} catch (SAXException e) {
// ignore the SAXException,
// it's already been captured by the error handling methods
// of this class (fatalError, error, warning)
} catch (IOException e) {
String sMsg = e.getMessage();
if (sMsg.length() == 0) {
sMsg = e.toString();
}
sMsg = "Error parsing XML document: "+sMsg;
ValidationError error = new ValidationError();
error.setMessage(sMsg);
error.setReasonCode(ValidationError.REASONCODE_XML_ISINVALID);
getValidationErrors().add(error);
throw new ValidationException(schema.getKey(),
sMsg,getValidationErrors());
}
}
if (getValidationErrors().size() > 0) {
throw new ValidationException(schema.getKey(),
"XSD violation.",getValidationErrors());
}
}
/**
* Validates an XML string against the XML Schema Definition file
* associated with a schema.
* @param schema the schema being validated
* @param xml the XML string to be validated
* @throws ValidationException if validation errors were located
*/
public void validate(Schema schema, String xml)
throws ValidationException {
_xsdLocation = null;
setValidationErrors(new ValidationErrors());
String sXsd = Val.chkStr(schema.getXsdLocation());
if (sXsd.length() > 0) {
int nIdx = sXsd.indexOf("[GML32]");
if (nIdx != -1) {
String sLeft = Val.chkStr(sXsd.substring(0,nIdx));
String sRight = Val.chkStr(sXsd.substring(nIdx+7));
sXsd = "";
if ((sLeft.length() > 0) && (sRight.length() > 0)) {
sXsd = sLeft;
if (xml != null) {
if (xml.contains("\"http://www.opengis.net/gml/3.2\"") ||
xml.contains("'http://www.opengis.net/gml/3.2'")) {
sXsd = sRight;
}
}
} else if (sLeft.length() > 0) {
sXsd = sLeft;
} else if (sRight.length() > 0) {
sXsd = sRight;
}
}
}
if (sXsd.length() > 0) {
_xsdLocation = sXsd;
StringReader reader = new StringReader(xml);
Source source = new SAXSource(new InputSource(reader));
validate(schema,source);
}
}
/**
* Triggered when a SAX warning occurs during XSD validation.
* <br/>Warnings are ignored.
* @param e the associated SAX exception
* @throws SAXException part of the implemented method signature but never thrown
*/
public void warning(SAXParseException e) throws SAXException {
// no behavior
}
}